package com.smile.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Consumer;

@Component
@Slf4j
public class GlobalAuthFilter implements GlobalFilter {
    // 线程安全写法
    private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String requestId = UUID.randomUUID().toString();

        String path = request.getURI().getPath();
        log.info("访问的路径为:{},request_id={}", path, requestId);

        HttpHeaders headers = exchange.getRequest().getHeaders();
        Set<Map.Entry<String, List<String>>> entries = headers.entrySet();
        System.out.println("header=" + entries);
        log.info("header={},request_id={}", headers, requestId);

        // 1. 拦截内部接口调用
        if (isInternalAccess(path)) {
            return handleForbidden(exchange, "禁止访问内部接口");
        }

        // 2. 处理Token
        LinkedHashMap<String, String> headersToAdd = new LinkedHashMap<>();
        processAuthHeader(request, headersToAdd);

        headersToAdd.put("request_id", requestId);

        // 3. 传递修改后的请求
        return chainFilterWithNewHeaders(chain, exchange, headersToAdd);
    }

    private boolean isInternalAccess(String path) {
        return ANT_PATH_MATCHER.match("**/inner/**", path);
    }

    private Mono<Void> handleForbidden(ServerWebExchange exchange, String message) {
        log.warn("内部接口拦截: {}", exchange.getRequest().getURI());
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.FORBIDDEN);
        return response.writeWith(Mono.just(
                response.bufferFactory().wrap(message.getBytes(StandardCharsets.UTF_8))
        ));
    }

    private void processAuthHeader(ServerHttpRequest request, Map<String, String> headersToAdd) {
        List<String> authHeaders = request.getHeaders().get("Authorization");
        if (CollectionUtils.isEmpty(authHeaders)) return;

        String authHeader = authHeaders.get(0);

//        headersToAdd.put("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
//        headersToAdd.put("Access-Control-Allow-Origin", "*");
        if (isBearerToken(authHeader)) {
            try {
                String token = authHeader.split(" ")[1];
                headersToAdd.put("access_token", token);
                headersToAdd.put("Authorization", "Bearer " + token);
            } catch (ArrayIndexOutOfBoundsException e) {
                log.error("Invalid token format: {}", authHeader);
            }
        }
    }

    private boolean isBearerToken(String authHeader) {
        return authHeader != null && authHeader.startsWith("Bearer ");
    }

    private Mono<Void> chainFilterWithNewHeaders(GatewayFilterChain chain,
                                                 ServerWebExchange exchange,
                                                 Map<String, String> headersToAdd) {
        if (headersToAdd.isEmpty()) {
            return chain.filter(exchange);
        }

        Consumer<HttpHeaders> headersConsumer = httpHeaders ->
                headersToAdd.forEach(httpHeaders::set);

        ServerHttpRequest newRequest = exchange.getRequest()
                .mutate()
                .headers(headersConsumer)
                .build();

        return chain.filter(exchange.mutate().request(newRequest).build());
    }
}
